package triTree;

import transaction.Transaction;
import triFileSystem.*;
import triInstance.NodeSnapshot;
import triInstance.TriModule;
import triStorage.*;
import triDataStruct.*;
import utils.*;

//Hash ? or other function to make the matching more efficient
public class NodeManager extends MemoryBuffer implements TriModule {

	private TriDiskFile diskfile;
	
	private TriMMPFile mmpfile;
	
	private int curNodeId;
	
	private NodeSnapshot rootSnapshot;
	private WritableTriNode root;

	private Object nodeIdLock = new Object();
	
	public NodeManager(int node_buffer_size, String node_filename, int node_page_buffer_size,
			int node_pagesize) {
		super(node_buffer_size);
		diskfile = new TriDiskFile(node_filename);
		//mmpfile = new TriMMPFile(node_filename, node_page_buffer_size, node_pagesize);
		mmpfile = new TriMMPFile(diskfile, node_page_buffer_size, node_pagesize);
	}
	
	@Override
	public void init(Transaction tx) {
		rootSnapshot = new NodeSnapshot(Constants.ROOT_NODEID, Constants.ROOT_TAGNAME, 
				Constants.ROOT_INODEPAGEID, Constants.ROOT_SET_INODEPAGEID);
		
		//if the file is newly-created
		if (mmpfile.open()) {
			int pageId = mmpfile.allocNewPage();
			int inodePageId = mmpfile.allocNewPage();
			if (pageId != 0 || inodePageId != Constants.ROOT_INODEPAGEID) {
				System.err.println("the file is not newly-created!");
			}

			curNodeId = Constants.ROOT_NODEID;
			//mmpfile.writeInt(0, 0, curNodeId);
			byte[] buffer = new byte[Constants.INT_SIZE];
			Converter.Int2Bytes(curNodeId, buffer, 0);
			diskfile.write(0, buffer, 0, Constants.INT_SIZE);
			
			Inode inode = new PageDir(inodePageId, mmpfile, mmpfile, tx);
			IWriter writer = new WPageArray(inode, mmpfile, tx);
			WritableTriNode node = new WritableTriNode(rootSnapshot, null, writer);
			node.flush(tx);
			writer.dispose();
		}
		else {
			curNodeId = mmpfile.readInt(0, 0);
			//System.out.println("current node Id = " + curNodeId);
		}
		//never unpin the root
		root = getNode(rootSnapshot, tx);
	}
	
	public WritableTriNode getRoot() {
		return root;
	}
	
	public WritableTriNode getNode(NodeSnapshot snapshot, Transaction tx) {
		return (WritableTriNode)buffer[this.get(snapshot.getNodeId(), snapshot, tx)];
	}
	
	public void unPin(WritableTriNode node) {
		if (node.bufferIndex >= 0) {
			synchronized (node) {
				this.unPin(node.bufferIndex);
			}
		}
	}
	
	//How to avoid create node with the same pageId?
	public NodeSnapshot createNewNode(WritableTriNode father, String tagName, int setInodePageId,
			Transaction tx) {
		int inodePageId;
		int nodeId;
		
		synchronized (nodeIdLock) {
			nodeId = ++curNodeId;
			mmpfile.writeInt(0, 0, curNodeId, tx);
			//System.out.println("create new node " + nodeId);
		}
		
		//create new node
		inodePageId = mmpfile.allocNewPage();
		
		NodeSnapshot value = new NodeSnapshot(nodeId, tagName, inodePageId, setInodePageId);
		Inode inode = new PageDir(inodePageId, mmpfile, mmpfile, tx);
		WPageArray writer = new WPageArray(inode, mmpfile, tx);
		WritableTriNode node = new WritableTriNode(value, father, writer);
		node.flush(tx);
		
		//modify the father
		//NodeMgr knows that there only exist one TriNode(father) at the time (w/ disk access ability)
		
		/*TriNode nodeF = getNode(father);
		nodeF.addChild(node.getNodeSnapshot());
		unPin(nodeF);*/
		father.addChild(node.getNodeSnapshot(), tx);
		
		//System.out.println("create done!");
		return value;
	}
	
	@Override
	protected BufferObject createEmptyObject(int index, Transaction tx) {
		return new WritableTriNode(index);
	}

	@Override
	protected void loadObject(int key, Object arg, int index, Transaction tx) {
		NodeSnapshot snapshot = (NodeSnapshot)arg;
		Inode inode = new PageDir(snapshot.getInodePageId(), mmpfile, mmpfile, tx);
		RPageArray pageArr = new RPageArray(inode, mmpfile);
		((WritableTriNode)buffer[index]).load(pageArr, tx);
	}

	@Override
	protected void flushObject(int index, Transaction tx) {
		//modification to node is write-through
	}


	@Override
	public void dispose(Transaction tx) {
		indexLock.lock();
	
		try {			
			for (int i = 0; i < buffer.length; ++i) 
				if (buffer[i] != null) {
					synchronized(buffer[i]) {
						this.flushObject(i, tx);
						((WritableTriNode)buffer[i]).dispose();
					}
				}
		} 
		finally {
			indexLock.unlock();
			mmpfile.close();
		}
	}

}
